package com.google.code.mochaccino.framework.xml.xpath;
/*
 * Copyright 2012 Claude Houle claude.houle@gmail.com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import com.google.code.mochaccino.framework.xml.TranscodingUtil;
import com.google.code.mochaccino.framework.xml.XMLToolkit;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Iterator;
import java.util.List;
import org.w3c.dom.DOMException;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.traversal.NodeFilter;
import org.w3c.dom.traversal.NodeIterator;

/**
 * The XPathEngine Base class contains basic logic for interfacing with an
 * JAXP Compliant XPath Engine.
 */
public abstract class XPathEngine {

	/** Select the Node Represented by that XPath */
	public abstract Node selectNode( Node node, String xpath ) throws XPathEngineException;

	/** Select the Nodes Represented by that XPath */
	public abstract NodeList selectNodeList( Node node, String xpath ) throws XPathEngineException;

	/**
	 * Select the Value of the XPath. If the XPath represent something else than an Attribute or a
	 * TextNode, then there will be an XPathEngineException sent to the user.
	 */
	public abstract String selectNodeValue( Node node, String xpath ) throws XPathEngineException;

	/** Select Nodes Values */
	public <T> Iterator<T> selectIterator( Node node, String xpath, @SuppressWarnings( "unused" ) Class<T> type ) throws XPathEngineException {
		return new IteratorValues<T>( this, selectIterator( node, xpath ) );
	}

	/** Select Iterator Node */
	public Iterator<Node> selectIterator( Node node, String xpath ) throws XPathEngineException {
		return new IteratorNode( selectNodeList( node, xpath ) );
	}

	/** Select Node Iterator */
	public NodeIterator selectNodeIterator( Node node, String xpath ) throws XPathEngineException {
		return new DOMNodeIterator( selectNodeList( node, xpath ) );
	}

	/**
	 * Select the Value of the XPath. If the XPath represent something else than an Attribute or a
	 * TextNode, then there will be an XPathEngineException sent to the user.
	 */
	public <T> T selectNodeValue( Node node, String xpath, Class<T> type ) throws XPathEngineException {
		return selectNodeValue( node, xpath, type, null );
	}

	/**
	 * Select the Value of the XPath. If the XPath represent something else than an Attribute or a
	 * TextNode, then there will be an XPathEngineException sent to the user.
	 */
	public <T> T selectNodeValue( Node node, String xpath, Class<T> type, T defaultValue ) throws XPathEngineException {
		return TranscodingUtil.decode( selectNodeValue( node, xpath ), type, defaultValue );
	}

	/**
	 * Select the Value of the XPath. If the XPath represent something else than an Attribute or a
	 * TextNode, then there will be an XPathEngineException sent to the user.
	 */
	public String selectNodeValue( Node node, String xpath, String defaultValue ) throws XPathEngineException {
		String result = selectNodeValue( node, xpath );
		if ( result == null ) {
			result = defaultValue;
		}
		return result;
	}

	/**
	 * A NodeIterator that iterates a NodeList
	 *
	 *
	 *
	 */
	protected static class DOMNodeIterator implements NodeIterator {

		private int      index    = -1;
		private NodeList nodeList = null;

		public DOMNodeIterator( NodeList list ) {
			nodeList = list;
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#detach()
		 */
		@Override
		public void detach() {
			nodeList = null;
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#getExpandEntityReferences()
		 */
		@Override
		public boolean getExpandEntityReferences() {
			return false;
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#getFilter()
		 */
		@Override
		public NodeFilter getFilter() {
			return null;
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#getRoot()
		 */
		@Override
		public Node getRoot() {
			return currentNode().getParentNode();
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#getWhatToShow()
		 */
		@Override
		public int getWhatToShow() {
			return 0;
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#nextNode()
		 */
		@Override
		public Node nextNode() throws DOMException {
			if ( nodeList == null ) {
				return null;
			}
			if ( (index + 1) < nodeList.getLength() ) {
				index++;
				return nodeList.item( index );
			}
			return null;
		}

		/* (non-Javadoc)
		 * @see org.w3c.dom.traversal.NodeIterator#previousNode()
		 */
		@Override
		public Node previousNode() throws DOMException {
			if ( nodeList == null ) {
				return null;
			}
			if ( index - 1 < 0 ) {
				return null;
			}
			index--;
			return nodeList.item( index );
		}

		/** Return the Current Node */
		public Node currentNode() throws DOMException {
			if ( nodeList == null ) {
				return null;
			}
			if ( (index + 1) < nodeList.getLength() ) {
				return nodeList.item( index );
			}
			return null;
		}
	}

	protected static class DOMNodeListImpl implements NodeList {

		private List<?> nodes = null;

		public DOMNodeListImpl( List<?> list ) {
			nodes = list;
		}

		@Override
		public int getLength() {
			return nodes.size();
		}

		@Override
		public Node item( int pIdx ) {
			return (Node) nodes.get( pIdx );
		}
	}

	protected static class IteratorNode implements Iterator<Node> {

		private int      index    = 0;
		private NodeList nodeList = null;

		/** Constructor */
		public IteratorNode( NodeList list ) {
			nodeList = list;
		}

		/*
		 *(non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		@Override
		public boolean hasNext() {
			return index < nodeList.getLength();
		}

		/*
		 *(non-Javadoc)
		 * @see java.util.Iterator#next()
		 */
		@Override
		public Node next() {
			if ( nodeList == null ) {
				return null;
			}
			if ( (index + 1) < nodeList.getLength() ) {
				index++;
				return nodeList.item( index );
			}
			return null;
		}

		/*
		 *(non-Javadoc)
		 * @see java.util.Iterator#remove()
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException( "'Remove' not supported" );
		}
	}

	protected static class IteratorValues<T> implements Iterator<T> {

		private Iterator<Node> nodes                = null;
		private Constructor<?> parameterConstructor = null;
		private Class<?>       parameterType        = null;
		private XMLToolkit toolkit              = null;

		/** Constructor */
		public IteratorValues( XPathEngine engine, Iterator<Node> list ) {
			toolkit = new XMLToolkit( engine );
			nodes = list;
			parameterType = IteratorValues.class.getTypeParameters()[0].getGenericDeclaration();
			try {
				parameterConstructor = parameterType.getConstructor( new Class[] { String.class } );
			} catch ( SecurityException e ) {
				throw new RuntimeException( e );
			} catch ( NoSuchMethodException e ) {
				throw new RuntimeException( e );
			}
		}

		/*
		 *(non-Javadoc)
		 * @see java.util.Iterator#next()
		 */
		@Override
		@SuppressWarnings( "unchecked" )
		public T next() {
			if ( !nodes.hasNext() ) {
				return null;
			}
			Node node = nodes.next();
			if ( node == null ) {
				return null;
			}

			String value = toolkit.getElementText( node );
			if ( value == null ) {
				return null;
			}
			try {
				return (T) parameterConstructor.newInstance( new Object[] { value } );
			} catch ( IllegalArgumentException e ) {
				throw new RuntimeException( e );
			} catch ( InstantiationException e ) {
				throw new RuntimeException( e );
			} catch ( IllegalAccessException e ) {
				throw new RuntimeException( e );
			} catch ( InvocationTargetException e ) {
				throw new RuntimeException( e );
			}
		}

		/*
		 *(non-Javadoc)
		 * @see java.util.Iterator#hasNext()
		 */
		@Override
		public boolean hasNext() {
			return nodes.hasNext();
		}

		/*
		 *(non-Javadoc)
		 * @see java.util.Iterator#remove()
		 */
		@Override
		public void remove() {
			throw new UnsupportedOperationException( "'Remove' not supported" );
		}
	}
}
